package webcontroller

import (
	"fmt"
	"net/http"
	"time"

	"github.com/its-a-feature/Mythic/authentication/mythicjwt"

	"github.com/gin-gonic/gin"
	"github.com/its-a-feature/Mythic/authentication"
	"github.com/its-a-feature/Mythic/database"
	databaseStructs "github.com/its-a-feature/Mythic/database/structs"
)

type GenerateAPITokenInput struct {
	Input GenerateAPIToken `json:"input" binding:"required"`
}

type GenerateAPIToken struct {
	TokenType  string `json:"token_type" binding:"required"`
	OperatorID int    `json:"operator_id"`
	Name       string `json:"name"`
}

type GenerateAPITokenResponse struct {
	Status       string    `json:"status"`
	TokenValue   string    `json:"token_value"`
	Error        string    `json:"error"`
	ID           int       `json:"id"`
	OperatorID   int       `json:"operator_id"`
	Name         string    `json:"name"`
	CreatedBy    int       `json:"created_by"`
	TokenType    string    `json:"token_type"`
	CreationTime time.Time `json:"creation_time"`
}

func GenerateAPITokenWebhook(c *gin.Context) {
	// get variables from the POST request
	var input GenerateAPITokenInput
	err := c.ShouldBindJSON(&input)
	if err != nil {
		c.JSON(http.StatusBadRequest, GenerateAPITokenResponse{
			Status: "error",
			Error:  err.Error(),
		})
		return
	}
	// get the associated database information
	// an api token is just the same as a JWT
	userID, err := GetUserIDFromGin(c)
	if err != nil {
		c.JSON(http.StatusOK, GenerateAPITokenResponse{
			Status: "error",
			Error:  err.Error(),
		})
		return
	}
	defaultName := "autogenerated"
	createdBy := userID
	if input.Input.OperatorID > 0 && input.Input.OperatorID != userID {
		targetOperator := databaseStructs.Operator{ID: input.Input.OperatorID}
		err = database.DB.Get(&targetOperator, "select * from operator where id = $1", targetOperator.ID)
		if err != nil {
			c.JSON(http.StatusOK, GenerateAPITokenResponse{
				Status: "error",
				Error:  err.Error(),
			})
			return
		}
		issuingUser := databaseStructs.Operator{ID: userID}
		err = database.DB.Get(&issuingUser, "select * from operator where id = $1", issuingUser.ID)
		if err != nil {
			c.JSON(http.StatusOK, GenerateAPITokenResponse{
				Status: "error",
				Error:  err.Error(),
			})
			return
		}
		if targetOperator.AccountType != databaseStructs.AccountTypeBot {
			c.JSON(http.StatusOK, GenerateAPITokenResponse{
				Status: "error",
				Error:  "account type is not bot",
			})
			return
		}
		if !issuingUser.Admin {
			c.JSON(http.StatusOK, GenerateAPITokenResponse{
				Status: "error",
				Error:  "only admins can generate API tokens for bot accounts",
			})
		}
		userID = targetOperator.ID
		defaultName = fmt.Sprintf("generated by %s", issuingUser.Username)
	}
	if input.Input.Name != "" {
		defaultName = input.Input.Name
	}
	claims, err := authentication.GetClaims(c)
	if err != nil {
		c.JSON(http.StatusForbidden, gin.H{"error": "Authentication Failed"})
		return
	}
	authType := mythicjwt.AUTH_METHOD_API
	if claims.EventStepInstanceID > 0 {
		authType = mythicjwt.AUTH_METHOD_EVENT
	}
	// save off the access_token as an API token and then return it
	apiToken := databaseStructs.Apitokens{
		TokenValue: "",
		OperatorID: userID,
		TokenType:  input.Input.TokenType,
		Active:     true,
		Name:       defaultName,
		CreatedBy:  createdBy,
	}
	statement, err := database.DB.PrepareNamed(`INSERT INTO apitokens 
		(token_value, operator_id, token_type, active, "name", created_by) 
		VALUES
		(:token_value, :operator_id, :token_type, :active, :name, :created_by)
		RETURNING id`)
	if err != nil {
		c.JSON(http.StatusOK, GenerateAPITokenResponse{
			Status: "error",
			Error:  err.Error(),
		})
		return
	}
	err = statement.Get(&apiToken.ID, apiToken)
	if err != nil {
		c.JSON(http.StatusOK, GenerateAPITokenResponse{
			Status: "error",
			Error:  err.Error(),
		})
		return
	}
	access_token, _, _, err := mythicjwt.GenerateJWT(databaseStructs.Operator{
		ID: userID,
	}, authType, claims.EventStepInstanceID, apiToken.ID)
	if err != nil {
		c.JSON(http.StatusOK, GenerateAPITokenResponse{
			Status: "error",
			Error:  err.Error(),
		})
		return
	}
	apiToken.TokenValue = access_token
	_, err = database.DB.Exec(`UPDATE apitokens SET token_value=$1 WHERE id=$2`, apiToken.TokenValue, apiToken.ID)
	if err != nil {
		c.JSON(http.StatusOK, GenerateAPITokenResponse{
			Status: "error",
			Error:  err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, GenerateAPITokenResponse{
		Status:       "success",
		TokenValue:   access_token,
		OperatorID:   userID,
		ID:           apiToken.ID,
		Name:         apiToken.Name,
		CreatedBy:    apiToken.CreatedBy,
		TokenType:    apiToken.TokenType,
		CreationTime: apiToken.CreationTime,
	})
}
